use std::collections::HashMap;

pub fn check_id_card(id_card_no: &str) -> String {
    let map: HashMap<u32, String> =
        serde_json::from_str(&std::fs::read_to_string("out.json").unwrap()).unwrap();
    let id = match Id::from_str(id_card_no) {
        Some(val) => val,
        None => return "身份证号码错误".to_string(),
    };

    let address = match map.get(&id.address_code) {
        Some(val) => val,
        None => return "身份证号码错误".to_string(),
    };

    let x = if id.index % 2 == 0 { "女" } else { "男" };
    format!(
        "身份证号码正确,{},{}年{:0>2}月{:0>2}日,{}",
        x, id.birth_day.year, id.birth_day.month, id.birth_day.day, address
    )
}

pub struct Id {
    address_code: u32,
    birth_day: DateTime,
    index: u16,
}

#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct DateTime {
    year: u32,
    month: u8,
    day: u8,
}

impl Id {
    pub fn from_str(text: &str) -> Option<Id> {
        if text.len() != 15 && text.len() != 18 {
            return None;
        }

        let address_code = u32::from_str_radix(text.get(0..6)?, 10).ok()?;
        let birth_day;
        let index;
        if text.len() == 15 {
            let year = 1900 + u32::from_str_radix(text.get(6..8)?, 10).ok()?;
            let month = u8::from_str_radix(text.get(8..10)?, 10).ok()?;
            let day = u8::from_str_radix(text.get(10..12)?, 10).ok()?;

            birth_day = DateTime { year, month, day };
            index = u16::from_str_radix(text.get(12..15)?, 10).ok()?;
        } else {
            if check(text)? != text.chars().rev().next()? {
                return None;
            }
            let year = u32::from_str_radix(text.get(6..10)?, 10).ok()?;
            let month = u8::from_str_radix(text.get(10..12)?, 10).ok()?;
            let day = u8::from_str_radix(text.get(12..14)?, 10).ok()?;

            birth_day = DateTime { year, month, day };
            index = u16::from_str_radix(text.get(14..17)?, 10).ok()?;
        };
        Some(Self {
            address_code,
            birth_day,
            index,
        })
    }
}

fn check(body: &str) -> Option<char> {
    let weights: [u32; 17] = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    let check_digits: [char; 11] = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
    let mut sum = 0;
    for (i, c) in body.chars().take(17).enumerate() {
        let digit = c.to_digit(10)?;
        sum += digit * weights[i];
    }

    Some(check_digits[(sum % 11) as usize])
}
